EXESKEL - EXE Skeleton


SUMMARY
=======

The EXESKEL sample introduces the basic application skeleton that can be
used as a point of departure for more complex OLE applications. It is used
as a base for the OLE Tutorial series of code samples. Of particular
interest in this code sample is the support for initializing and
uninitializing the OLE Libraries. The general use of APPUTIL to construct
this application is also worthy of study.

For functional descriptions and a tutorial code tour of EXESKEL, see the
Code Tour section below. For details on external user operation of EXESKEL,
see the Operation section below.

In general, to set up your system to build and test the code samples in this
OLE Tutorial series, see TUTORIAL.TXT for details. The accompanying makefile
is Microsoft NMAKE-compatible. To create a debug build of EXESKEL, issue
the NMAKE command at the command prompt.

Usage
-----

EXESKEL is an .EXE file that you can execute directly from Windows or from
the command prompt. No command line arguments are recognized by EXESKEL.

OPERATION
=========

Menu Selection: File/Exit
Quits EXESKEL.

Menu Selection: Help/Read EXESKEL.TXT
Starts the Windows Notepad and displays EXESKEL.TXT (this file).

Menu Selection: Help/Read Source File
Starts the Windows Notepad and displays a selected EXESKEL source file.
This command illustrates how to program the use of the File Open common
dialog box.

Menu Selection: Help/About EXESKEL
Displays the About dialog box for this application, a standard part of
this series of code samples. The code illustrates how to program the use
of the CAboutBox class provided by APPUTIL.LIB.


CODE TOUR
=========

Files          Description

EXESKEL.TXT    This file.
MAKEFILE       The generic makefile for building the code sample
               application of this tutorial lesson.
EXESKEL.H      The include file for the EXESKEL application. Contains
               class declarations, function prototypes, and resource
               identifiers.
EXESKEL.CPP    The main implementation file for EXESKEL.EXE. Has WinMain
               and CMainWindow implementation.
EXESKEL.RC     The application resource definition file.
EXESKEL.ICO    The application icon resource.

EXESKEL is meant to be a generic point of departure for building Win32 C++
applications that support OLE. It is the basic skeleton that is built
upon in subsequent code samples in this tutorial series. You can study
the code comments to learn more about this C++ application skeleton.

EXESKEL.CPP defines the WinMain entry function for the entire application,
which contains the message loop. EXESKEL makes use of many of the utility
classes and services provided by APPUTIL. For more details on APPUTIL,
study the source code located in the APPUTIL directory, a sibling to
EXESKEL, and APPUTIL.TXT.

EXESKEL.H exploits APPUTIL's CVirWindow abstract base class (see
APPUTIL.H) to derive and implement a CMainWindow class. CMainWindow
encapsulates the main window functionality into a convenient C++ object.
It handles initialization of new instances of the main window and the
message dispatching of the main window procedure. This main window
procedure is a method of CMainWindow.

In the context of an OLE Tutorial series of code samples, the goal of
EXESKEL is to show the initial and final code you need so your application
can properly use the OLE libraries and implement OLE interfaces.

Before the OLE libraries can be used, they must be initialized. Since the
OLE Libraries allocate resources, you must uninitialize the libraries just
before exiting the application to release any resources allocated by the
libraries and to unload the OLE DLL libraries themselves.

For EXESKEL, the code for initializing and uninitializing the OLE
libraries is found entirely in the WinMain function, located in
EXESKEL.CPP. The following code fragment found at the very start of
WinMain illustrates how to check for initialization of the OLE libraries
and to exit gracefully if the libraries cannot be initialized properly.

  // Call to initialize the OLE COM Library. Use the OLE SUCCEEDED macro
  // to detect success.
  if (SUCCEEDED(CoInitialize(NULL)))
  {
    // If we succeeded in initializing the COM Library we proceed to
    //   initialize and run the application.
    ...
    ...
    ...
  }
  else
  {
    // We failed to Initialize the OLE COM Library.
    TCHAR szMsg[MAX_STRING_LENGTH];

    // Load the error message string from the resources.
    if (LoadString(hInstance, IDS_OLEINITFAILED, szMsg, MAX_STRING_LENGTH))
    {
      // Put up error message box saying that OLE COM Library
      //   couldn't be initialized.  Parent window is desktop (ie, NULL).
      MessageBox(
        NULL,
        szMsg,
        TEXT(ERROR_TITLE_STR),
        MB_OK | MB_ICONEXCLAMATION);
    }
    // And exit the failed application (ie, by returning FALSE to WinMain).
    bRun = FALSE;
  }

For Win32 applications, the pvReserved parameter to the CoInitialize call
must always be passed as NULL. The parameter isn't used in 32-bit OLE, and
CoInitialize will return E_INVALIDARG if you pass a non-NULL parameter.
The OLE SUCCEEDED macro is used to check the CoInitialize return code for
success. The hPrevInstance parameter of WinMain is not used because under
Win32, it is always NULL. Other techniques must be used if you want to
allow only one instance of the application to run. In this case, multiple
instances of EXESKEL are permitted.

Once the checks and COM initialization are done, the application
continues. Just before it exits, it must call CoUninitialize. Such exits
could occur in many places in the program. The final normal program exit
usually occurs after the application's message loop is terminated. The
following code fragment shows this CoUninitialize call. It's a simple
call that takes no parameters.

    // We're exiting this app (either normally or by init failure) so shut
    // down the OLE COM Library.
    CoUninitialize();

CoInitialize could be called more than once in an application. The first
successful call will return NOERROR; subsequent successful calls will
return S_FALSE. Each successful nested call to CoInitialize must be
matched with a corresponding call to CoUninitialize. CoUninitialize frees
resources the OLE COM libraries have allocated and unloads the libraries
themselves.

The EXESKEL application calls CoInitialize and CoUnitialize because it
uses only COM services in the COM library (currently part of OLE32.DLL).
If the application will be exploiting OLE technology--such as OLE
automation, compound documents, in-place activation, linking, embedding,
and drag-and-drop--the OleInitialize call must be used instead. The
OleInitialize function loads the OLE libraries, including the COM library.
Thus OleInitialize calls CoInitialize internally, and OleUninitialize
calls CoUninitialize internally.

There is stub code in the DoMenu method of EXESKEL.CPP for opening the
application's help file. This code is essentially a placeholder. The
EXESKEL.CPP code assembles the proper help file name, as in the following
fragment from CMainWindow::InitInstance:

      // Build a path to where the help file should be (it should be in
      // the same directory as the .EXE but with the .HLP extension.
      MakeFamilyPath(hInstance, m_szHelpFile, TEXT(HELP_FILE_EXT));

MakeFamilyPath is a utility function in the APPUTIL.LIB library that uses
GetModuleFileName to dynamically determine the path to the executing
module and then construct a proper "family" variant of that path with the
proper file name extension for a particular purpose. In this case,
HELP_FILE_EXT (.HLP) is used.  Various such extensions are defined in
APPUTIL.H for use with the MakeFamilyPath function.

If support for the case IDM_HELP_CONTENTS: menu selection is provided in
the EXESKEL.RC file, the following code attempts to find and run WinHelp
on that EXESKEL.HLP file.

    case IDM_HELP_CONTENTS:
      // We have some stubbed support here for bringing up the online
      // Help for this application.
      if (::FileExist(m_szHelpFile))
        ::WinHelp(m_hWnd, m_szHelpFile, HELP_CONTEXT, IDH_CONTENTS);
      else
        m_pMsgBox->ErrorID(IDS_NOHELPFILE);
      break;

This code fragment illustrates the use of the Error message box. The
EXESKEL makefile does not produce the help file in this and other code
samples, and the EXESKEL.RC file has the Help Contents menu item commented
out.

At the very start of WinMain, the UnicodeOk function call is made to
determine if the application was compiled with UNICODE defined and is
running on a platform where Win32 string functions are supported for
Unicode.

The UnicodeOk function is defined and used only within the EXESKEL.CPP
module.

  BOOL UnicodeOk(void)
  {
    BOOL bOk = TRUE;
    TCHAR szUserName[MAX_STRING_LENGTH];
    DWORD dwSize = MAX_STRING_LENGTH;

    if (!GetUserName(szUserName, &dwSize))
      bOk = ERROR_CALL_NOT_IMPLEMENTED == GetLastError() ? FALSE : TRUE;

    return bOk;
  }

This function uses a representative Unicode-supported Win32 API function
call (GetUserName). This call reduces to GetUserNameW when UNICODE is
defined and reduces to GetUserNameA when UNICODE is not defined. If a
call to GetLastError returns ERROR_CALL_NOT_IMPLEMENTED, the implication
is that the platform won't handle other Unicode calls very well either,
and UnicodeOK returns FALSE.

Here's the code at the start of WinMain that uses the check:

  // If we were compiled for UNICODE and the platform seems OK with this,
  // then proceed.  Otherwise, we error, and exit the application.
  if (UnicodeOk())
  {
    ...
    ...
    ...
  }
  else
  {
    // If we were compiled for UNICODE but the platform has problems with
    // this, then indicate an error and exit the application immediately.
    CHAR szMsg[MAX_STRING_LENGTH];

    if (LoadStringA(
          hInstance,
          IDS_UNICODEFAIL,
          szMsg,
          MAX_STRING_LENGTH))
    {
      MessageBoxA(
        NULL,
        szMsg,
        ERROR_TITLE_STR,
        MB_OK | MB_ICONEXCLAMATION);
    }
  }

Notice that the xxxA (ie, ANSI) versions of the LoadString and MessageBox
Win32 API calls are used explicitly. This is because the macro versions
of these calls (LoadString and MessageBox), when compiled with UNICODE
defined, will expand to LoadStringW and MessageBoxW. If the platform does
not support these xxxW calls (the ones sensitive to Unicode), they will
fail with GetLastError() returning ERROR_CALL_NOT_IMPLEMENTED. Many Win32
platforms support Unicode, and more will do so over time. The Unicode
(xxxW) versions of the Win32 API are fully implemented under Windows NT.
